/*
** Xin YUAN, 2023, BSD (2)
*/

////////////////////////////////////////////////////////////////////////////////

//client_base

class client_base
{
private:
	enum {
		MAX_SOCK_NUM = WSA_MAXIMUM_WAIT_EVENTS - 2,
		MAX_CHANGED_NUM = MAX_SOCK_NUM * 2 + 1
	};

protected:
	typedef client_util<MAX_SOCK_NUM, MAX_CHANGED_NUM>  utilClass;

protected:
	client_base() noexcept : m_lExit(0)
	{
	}
	client_base(const client_base&) = delete;
	~client_base() noexcept
	{
		uintptr_t size = m_arrEvent.GetSize();
		assert( size == 0 || size == 2 );
		if ( size == 2 ) {
			WSAEvent evt(m_arrEvent[0]);
			evt.Close();
			evt.Attach(m_arrEvent[1]);
			evt.Close();
		}
	}

	client_base& operator=(const client_base&) = delete;

protected:
	bool init(utilClass::client_callback cb) noexcept
	{
		assert( m_arrSocket.GetSize() == 0 );
		assert( m_arrHandle.GetSize() == 0 );
		uintptr_t size = m_arrEvent.GetSize();
		assert( size == 0 || size == 2 );
		if ( size == 0 ) {
			if ( !m_arrEvent.Append(2) )
				return false;
			WSAEvent evt[2];
			if ( !evt[0].Create() ) {
				m_arrEvent.Clear();
				return false;
			}
			if ( !evt[1].Create() ) {
				m_arrEvent.Clear();
				return false;
			}
			if ( !m_util.init_mutex() ) {
				m_arrEvent.Clear();
				return false;
			}
			m_arrEvent.GetAt(0) = evt[0].Detach();
			m_arrEvent.GetAt(1) = evt[1].Detach();
		}
		m_util.reset_changed();
		::InterlockedCompareExchange(&m_lExit, 0, 1);
		m_util.callback = cb;
		return true;
	}

public:
	void Run(uint32_t uTimeout)
	{
		//timeout
		constexpr const uint32_t c_min_timeout = 2 * 60 * 1000;  //2 minutes
		if ( uTimeout < c_min_timeout )
			uTimeout = c_min_timeout;

		//loop
		while ( true ) {
			uintptr_t size = m_arrEvent.GetSize();
			assert( size >= 2 );

			//wait
			DWORD dwRet = WSAEvent::Wait((DWORD)size, m_arrEvent.GetAddress(),
							FALSE, uTimeout);  //may block
			if ( dwRet == WSA_WAIT_FAILED ) {
				m_util.callback(utilClass::CB_WAIT_ERROR, NULL, false, this);  //may throw
				break;
			}
			if ( dwRet == WSA_WAIT_TIMEOUT ) {
				m_util.callback(utilClass::CB_TIMEOUT, NULL, false, this);  //may throw
				continue;
			}

			//first
			DWORD dwIndex = dwRet - WSA_WAIT_EVENT_0;
			if ( dwIndex == 0 ) { //exit
				WSAEvent evt(m_arrEvent.GetAt(dwIndex));
				evt.Reset();  //no check
				evt.Detach();
				break;
			}

			handle_data_base* p = NULL;
			//second
			if ( dwIndex == 1 ) {
				WSAEvent evt(m_arrEvent.GetAt(dwIndex));
				evt.Reset();  //no check
				evt.Detach();
				//changed
				m_util.lock_mutex();
				while ( !m_util.is_no_changed() ) {
					p = m_util.get_changed();
					//connect
					if ( p->op_type == OP_TYPE_CONNECT ) {
						m_util.unlock_mutex();
						if ( !insert_handle(p)
							|| !p->sock.GetSocket().SelectEvents(m_arrEvent.GetAt(p->index + 2), FD_READ | FD_WRITE | FD_CONNECT | FD_CLOSE) //to nonblocking socket
							) {
							m_util.lock_mutex();
							p->op_type = OP_TYPE_CLOSE;
						}
						else {
							m_util.lock_mutex();
							p->op_type = OP_TYPE_NONE;
						}
						m_util.dec_connect_changed();
					}
					//actual write
					while ( p->op_type == OP_TYPE_SEND ) {
						p->op_type = OP_TYPE_NONE;
						if ( p->send_len == 0 )
							break;
						m_util.unlock_mutex();
						DWORD dwSent = 0;
						if ( !p->sock.GetSocket().Write(p->send_buffer + p->act_sent_len, p->send_len - p->act_sent_len, &dwSent, NULL) ) {
							if ( ::WSAGetLastError() == WSAEWOULDBLOCK ) {
								m_util.lock_mutex();
								break;
							}
							m_util.callback(utilClass::CB_SENT, p, false, this);  //may throw
							m_util.lock_mutex();
						}
						else {
							m_util.lock_mutex();
							p->act_sent_len += dwSent;
							if ( p->act_sent_len >= p->send_len ) {
								m_util.unlock_mutex();
								m_util.callback(utilClass::CB_SENT, p, true, this);  //may throw
								m_util.lock_mutex();
							}
							else {
								p->op_type = OP_TYPE_SEND;
							}
						}
					}
					//close
					if ( p->op_type == OP_TYPE_CLOSE ) {
						m_util.unlock_mutex();
						if ( p->is_valid_index() ) {
							close_handle_index(p->index);
							remove_handle(p->index);
						}
						else {
							close_handle(p);
						}
						m_util.callback(utilClass::CB_CLOSED, p, true, this);  //may throw
						m_util.lock_mutex();
					}
					if ( p->op_type != OP_TYPE_CONNECT )
						m_util.skip_changed();  //next
				} //end while
				m_util.unlock_mutex();
				continue;
			}

			//check
			p = m_arrHandle.GetAt(dwIndex - 2);
			WSANETWORKEVENTS wnetevents;
			if ( p->sock.GetSocket().EnumNetworkEvents(m_arrEvent.GetAt(dwIndex), &wnetevents) ) {
				//connect
				if ( wnetevents.lNetworkEvents & FD_CONNECT )
					m_util.callback(utilClass::CB_CONNECTED, p, wnetevents.iErrorCode[FD_CONNECT_BIT] == 0, this);  //may throw
				//read
				if ( wnetevents.lNetworkEvents & FD_READ ) {
					m_util.lock_mutex();
					if ( p->op_type != OP_TYPE_CLOSE ) {
						m_util.unlock_mutex();
						if ( wnetevents.iErrorCode[FD_READ_BIT] != 0 ) {
							m_util.callback(utilClass::CB_RECV, p, false, this);  //may throw
						}
						else {
							DWORD dwRead = 0;
							if ( !p->sock.GetSocket().Read(p->recv_buffer, HANDLE_BUFFER_SIZE, &dwRead, NULL) ) {
								if ( ::WSAGetLastError() != WSAEWOULDBLOCK )
									m_util.callback(utilClass::CB_RECV, p, false, this);  //may throw
							}
							else {
								p->recv_len = dwRead;
								if ( p->recv_len == 0 )
									_MarkCloseOperation(p);
								else
									m_util.callback(utilClass::CB_RECV, p, true, this);  //may throw
							}
						}
					}
					else {
						m_util.unlock_mutex();
					}
				}
				//write
				if ( wnetevents.lNetworkEvents & FD_WRITE ) {
					m_util.lock_mutex();
					if ( p->op_type != OP_TYPE_CLOSE ) {
						m_util.unlock_mutex();
						if ( wnetevents.iErrorCode[FD_WRITE_BIT] != 0 ) {
							m_util.callback(utilClass::CB_SENT, p, false, this);  //may throw
						}
						else {
							m_util.lock_mutex();
							m_util.set_changed(p, OP_TYPE_SEND);
							m_util.unlock_mutex();
							set_event(1);
						}
					}
					else {
						m_util.unlock_mutex();
					}
				}
				//close
				if ( wnetevents.lNetworkEvents & FD_CLOSE
					&& wnetevents.iErrorCode[FD_CLOSE_BIT] == 0 ) {
					m_util.lock_mutex();
					while ( p->op_type != OP_TYPE_CLOSE ) {
						m_util.unlock_mutex();
						DWORD dwRead = 0;
						if ( !p->sock.GetSocket().Read(p->recv_buffer, HANDLE_BUFFER_SIZE, &dwRead, NULL) ) {
							_MarkCloseOperation(p);
						}
						else {
							p->recv_len = dwRead;
							if ( p->recv_len == 0 )
								_MarkCloseOperation(p);
							else
								m_util.callback(utilClass::CB_RECV, p, true, this);  //may throw
						}
						m_util.lock_mutex();
					}
					m_util.unlock_mutex();
				}
			}
			else {
				WSAEvent evt(m_arrEvent.GetAt(dwIndex));
				evt.Reset();  //no check
				evt.Detach();
			}
		} //end while

		//clear
		clear_all_handles();  //may throw
	}

	void SetExitEvent() noexcept
	{
		set_event(0);
	}

	bool StartConnect(handle_data_base* p, const char_s* host, uint32_t port) noexcept
	{
		//check
		if ( ::InterlockedCompareExchange(&m_lExit, 1, 1) )
			return true;

		bool bWait = true;
		//addr
		SOCKADDR_STORAGE ss;
		int len;
		if ( !host_to_sockaddr(host, port, ss, len) )
			return false;
		//create
		if ( !p->sock.GetSocket().Create(AF_INET, SOCK_STREAM, IPPROTO_TCP) )
			return false;
		//non-blocking
		u_long mode = 1;
		if ( ::ioctlsocket(p->sock.GetSocket(), FIONBIO, &mode) != 0 ) {
			p->sock.GetSocket().Close();
			return false;
		}
		//connect
		if ( !p->sock.GetSocket().Connect((SOCKADDR*)&ss, len) ) {
			if ( ::WSAGetLastError() != WSAEWOULDBLOCK ) {
				p->sock.GetSocket().Close();
				return false;
			}
		}
		else {
			bWait = false;
		}
		(void)bWait;

		//set changed
		m_util.lock_mutex();
		p->op_type = OP_TYPE_NONE;
		p->send_len = p->act_sent_len = 0;
		bool bPost = m_util.set_changed(p, OP_TYPE_CONNECT);
		m_util.unlock_mutex();
		if ( !bPost ) {
			p->sock.GetSocket().Close();
			return false;
		}
		set_event(1);

		return true;
	}

	bool BeginInput(handle_data_base* p) noexcept
	{
		//check
		if ( ::InterlockedCompareExchange(&m_lExit, 1, 1) )
			return true;
		//lock
		m_util.lock_mutex();
		if ( p->send_len != 0 ) {
			m_util.unlock_mutex();
			return false;
		}
		return true;
	}
	void EndInput(handle_data_base* p) noexcept
	{
		m_util.set_changed(p, OP_TYPE_SEND);  //no check
		m_util.unlock_mutex();
		set_event(1);
	}

protected:
	void _FinishSend(handle_data_base* p) noexcept
	{
		m_util.lock_mutex();
		p->send_len = 0;
		p->act_sent_len = 0;
		m_util.unlock_mutex();
	}

	void _MarkCloseOperation(handle_data_base* p) noexcept
	{
		m_util.lock_mutex();
		m_util.set_changed(p, OP_TYPE_CLOSE);  //no check
		m_util.unlock_mutex();
		set_event(1);
	}

private:
	//index: 0 or 1
	void set_event(uintptr_t index) noexcept
	{
		WSAEvent evt(m_arrEvent.GetAt(index));
		evt.Set();  //no check
		evt.Detach();
	}

	//handles
	bool insert_handle(handle_data_base* p) noexcept
	{
		assert( !p->is_valid_index() );
		//full
		uintptr_t size = m_arrSocket.GetSize();
		if ( size >= MAX_SOCK_NUM )
			return false;
		//find
		SOCKET s = p->sock.GetSocket();
		uintptr_t uIndex = 0;
		bool bFound = CTL::ArrayHelper::BinarySearch(m_arrSocket.GetAddress(), size,
								s, uIndex);
		(void)bFound;
		assert( !bFound );
		//array
		WSAEvent evt;
		if ( !evt.Create() )
			return false;
		if ( !m_arrSocket.InsertAt(uIndex) )
			return false;
		if ( !m_arrHandle.InsertAt(uIndex) ) {
			m_arrSocket.RemoveAt(uIndex);
			return false;
		}
		if ( !m_arrEvent.InsertAt(uIndex + 2) ) {
			m_arrSocket.RemoveAt(uIndex);
			m_arrHandle.RemoveAt(uIndex);
			return false;
		}
		m_arrSocket.GetAt(uIndex) = s;
		m_arrHandle.GetAt(uIndex) = p;
		m_arrEvent.GetAt(uIndex + 2) = evt.Detach();
		//index
		p->index = uIndex;
		for ( uintptr_t i = uIndex + 1; i < m_arrHandle.GetSize(); i ++ )
			++ (m_arrHandle[i]->index);
		return true;
	}
	//index: index of handle array
	void remove_handle(uintptr_t index) noexcept
	{
		m_arrHandle[index]->index = (uintptr_t)-1;
		m_arrSocket.RemoveAt(index);
		m_arrHandle.RemoveAt(index);
		m_arrEvent.RemoveAt(index + 2);
		for ( uintptr_t i = index; i < m_arrHandle.GetSize(); i ++ )
			-- (m_arrHandle[i]->index);
	}
	static void close_handle(handle_data_base* p) noexcept
	{
		p->sock.GetSocket().Disable(SD_BOTH);  //no check
		p->sock.GetSocket().Close();
	}
	//index: index of handle array
	void close_handle_index(uintptr_t index) noexcept
	{
		handle_data_base* p = m_arrHandle.GetAt(index);
		WSAEvent evt(m_arrEvent[index + 2]);
		p->sock.GetSocket().SelectEvents(evt, 0);
		close_handle(p);
		evt.Close();
	}

//with callbacks/overriders

	void clear_all_handles()
	{
		::InterlockedCompareExchange(&m_lExit, 1, 0);
		//handles
		for ( uintptr_t i = 0; i < m_arrHandle.GetSize(); i ++ ) {
			//set type
			m_util.lock_mutex();
			m_arrHandle[i]->op_type = OP_TYPE_CLOSE;
			m_util.unlock_mutex();
			//close
			close_handle_index(i);
			m_arrHandle[i]->index = (uintptr_t)-1;
			m_util.callback(utilClass::CB_CLOSED, m_arrHandle[i], true, this);  //may throw
		}
		//changed
		m_util.lock_mutex();
		while ( !m_util.is_no_changed() ) {
			handle_data_base* p = m_util.get_changed();
			if ( p->op_type == OP_TYPE_CONNECT ) {
				p->op_type = OP_TYPE_CLOSE;
				m_util.unlock_mutex();
				close_handle(p);
				m_util.callback(utilClass::CB_CLOSED, p, true, this);  //may throw
				m_util.lock_mutex();
			}
			m_util.skip_changed();
		}
		m_util.unlock_mutex();
		//array
		m_arrEvent.SetSize(2);  //no check
		m_arrSocket.Clear();
		m_arrHandle.Clear();
	}

private:
	volatile LONG m_lExit;

	//handles
	CTL::PlainArray<SOCKET> m_arrSocket;
	CTL::PlainArray<handle_data_base*> m_arrHandle;
	CTL::PlainArray<WSAEVENT> m_arrEvent;

	//utility
	utilClass m_util;
};

////////////////////////////////////////////////////////////////////////////////
